﻿namespace ZGDisassembler.Core;

/// <summary>
/// 请实现特性，例如 [Platform("6502")]
/// </summary>
public class Platform
{
	/// <summary>
	/// 操作码与操作指令映射
	/// </summary>
	private static Dictionary<byte, AddressingMode> OperationMap { get; set; } = new();
	/// <summary>
	/// 编译环境
	/// </summary>
	protected AppEnv env { get; set; }

	public Platform(AppEnv env) 
	{ 
		this.env = env;
		OperationMap.Clear();
	}

	/// <summary>
	/// 平台初始化，用于加入反汇编的内容
	/// </summary>
	public virtual void Initialize() => throw new NotImplementedException("请重写该方法");
	

	/// <summary>
	/// 将数值转换成16进制值
	/// </summary>
	/// <param name="value">要转换的数值</param>
	/// <param name="length">转换长度</param>
	public virtual string ToHexString(uint value, byte length = 1) => value.ToString($"X{length * 2}");
	

	#region 增加反汇编内容
	/// <summary>
	/// 增加反汇编内容
	/// </summary>
	/// <param name="codes">汇编码</param>
	/// <param name="instruction">汇编指令</param>
	/// <param name="property">寻址属性</param>
	internal static void AddDisasmCode(Span<byte> codes, string instruction, AddressingModeProperty? property = null)
	{
		property ??= new();
		property.Instruction = instruction;

		var indexCode = codes[0];
		if (!OperationMap.TryGetValue(indexCode, out var subCodes))
		{
			subCodes = new AddressingMode();
			OperationMap.Add(indexCode, subCodes);
		}

		var restCode = "";
		var value = Utils.CombineHex(codes[1..]);
		if (value != null)
			restCode = value.Value.ToString("X");

		if (restCode.Length % 2 == 1)
			restCode = "0" + restCode;
		
		if (restCode.Length / 2 > subCodes.MaxLength)
			subCodes.MaxLength = (byte)(restCode.Length / 2);

		subCodes.SubProperty.TryAdd(restCode, property);
	}
	#endregion 增加反汇编内容

	#region 解码一行数据
	/// <summary>
	/// 解码一行数据
	/// </summary>
	/// <param name="orgAddress">编译起始地址</param>
	/// <param name="baseAddress">数据索引</param>
	/// <param name="length">强制读取长度</param>
	/// <returns></returns>
	public ResultLine? Decode(uint orgAddress, uint baseAddress, byte? length = null)
	{
		var opCode = env.GetData(baseAddress);
		if (opCode == null)
			return null;

		var result = new ResultLine { Addressings = new uint[] { opCode[0] } };
		if (!OperationMap.TryGetValue(opCode[0], out var mode))
			return result;

		baseAddress++;
		byte lengthMax = mode.MaxLength;
		byte lengthMin = 0;
		if (env.AllRecodeLine.TryGetValue(orgAddress, out var line))
		{
			switch(line.Type)
			{
				case DecodeType.Data:
					var temp = new List<uint>();
					for (uint i = 0; i < line.Length; i++)
					{
						var data = env.GetData(baseAddress + i);
						if (data.Length == 0)
						{
							result.Addressings = temp.ToArray();
							return result;
						}

						temp.Add(data[0]);
					}
					result.Addressings = temp.ToArray();
					break;
				case DecodeType.Instruction:
					lengthMax = lengthMin = line.Length;
					break;
			}
		}

		for (byte readLength = lengthMax; readLength >= lengthMin; --readLength)
		{
			var codes = env.GetData(baseAddress, readLength);
			if (codes == null)
				break;

			var byteCodes = "";
			var bytes = Utils.CombineHex(codes);
			if (bytes != null)
				byteCodes = bytes.Value.ToString("X");
			
			if (byteCodes.Length % 2 != 0)
				byteCodes = "0" + byteCodes;

			if (!mode.SubProperty.TryGetValue(byteCodes, out var property))
				continue;

			var temp = property.SpProcess?.Invoke(property, orgAddress, baseAddress, length);
			if (temp != null)
				return temp;

			if (property.AddressingMode != null)
			{
				var resultValues = new List<uint>();
				for (uint i = 0, offset = 0; i < property.AddressLength.Length; ++i)
				{
					var addressCode = env.GetData(baseAddress + readLength + offset, property.AddressLength[i]);
					if (addressCode == null)
						return result;

					var value = Utils.CombineHex(addressCode);
					resultValues.Add(value.Value);
					offset += property.AddressLength[i];
				}
				result.Addressings = resultValues.ToArray();
				result.TotalLength += (byte)(mode.MaxLength + property.AllAddressLength());
			}

			result.LineType = DecodeType.Instruction;
			result.SourceMode = property;
			break;
		}
		return result;
	}
	#endregion 解码一行数据

}

[AttributeUsage(AttributeTargets.Class)]
public class PlatformAttribute : Attribute
{
	public PlatformAttribute(string platformName) => Name = platformName;
	public string Name;
}
